@using System.Text
@using BlazorDatasheet.Render
@using Region = BlazorDatasheet.DataStructures.Geometry.Region
@using System.ComponentModel
@using BlazorDatasheet.Core.Data
@using BlazorDatasheet.Core.Events.Input
@using BlazorDatasheet.DataStructures.Geometry
@using BlazorDatasheet.Events
@using BlazorDatasheet.Services
@using Microsoft.JSInterop
@inject IJSRuntime JS;
@implements IDisposable

<!-- Render the temp selection (currently selected region) -->
@if (Sheet != null && BoundedSelectingRegion != null && BoundedSelectingRegion?.Area > 0)
{
    <!-- We split into regions around the cell's start position, so that the cell's 
    start position shows the renderer underneath it -->
    var brokenRegions = BoundedSelectingRegion.Break(GetRegion(Sheet.Selection.SelectingStartPosition));
    foreach (var region in brokenRegions)
    {
        <BoxOverlayRenderer
            BackgroundVisible="@true"
            BackgroundStyle="@_bgStyle"
            BorderThickness="0"
            X="GetLeft(region)"
            Y="GetTop(region)"
            Width="GetWidth(region)"
            Height="GetHeight(region)"/>
    }

    <!-- now render the border around the whole thing -->
    <BoxOverlayRenderer
        BackgroundVisible="@false"
        BorderThickness="2"
        BackgroundStyle="@_bgStyle"
        X="GetLeft(BoundedSelectingRegion)"
        Y="GetTop(BoundedSelectingRegion)"
        Width="GetWidth(BoundedSelectingRegion)"
        Height="GetHeight(BoundedSelectingRegion)"/>
}

<!-- render the selections that exist in the sheet -->
@if (Sheet?.Selection != null && !Sheet.Selection.IsEmpty())
{
    foreach (var region in Sheet.Selection.Regions)
    {
        var boundedRegion = region.GetIntersection(Sheet.Region);
        var isActiveRegion = region.Equals(Sheet.Selection.ActiveRegion);
        if (boundedRegion == null)
            continue;
        <!-- if it's the active region, render around the active position -->
        if (isActiveRegion)
        {
            var brokenRegions = boundedRegion
                .Break(GetRegion(Sheet.Selection.ActiveCellPosition));
            foreach (var brokenRegion in brokenRegions)
            {
                <BoxOverlayRenderer
                    BackgroundStyle="@_bgStyle"
                    BackgroundVisible="true"
                    BorderThickness="0"
                    X="GetLeft(brokenRegion)"
                    Y="GetTop(brokenRegion)"
                    Width="GetWidth(brokenRegion)"
                    Height="GetHeight(brokenRegion)"/>
            }
        }

        <!-- now render the border around the whole region. No fill on active region because we've filled it already -->
        <BoxOverlayRenderer
            BackgroundVisible="@(!isActiveRegion)"
            BorderThickness="@(isActiveRegion ? 2 : 0)"
            BackgroundStyle="@_bgStyle"
            X="GetLeft(boundedRegion)"
            Y="GetTop(boundedRegion)"
            Width="GetWidth(boundedRegion)"
            Height="GetHeight(boundedRegion)"/>
    }
}

<!-- render dragger (bottom right corner) -->
<div id="auto-filler"
     @onpointerdown="DraggerMouseDown"
     style="@GetDraggerStyleString();cursor:crosshair;pointer-events: all;">
</div>

<!-- drag preview -->
@if (_isDragging && _dragPreviewRegion != null)
{
    <BoxOverlayRenderer
        BackgroundVisible="false"
        BorderThickness="1"
        X="GetLeft(_dragPreviewRegion)"
        Y="GetTop(_dragPreviewRegion)"
        Width="GetWidth(_dragPreviewRegion)"
        Height="GetHeight(_dragPreviewRegion)"/>
}

@code {

    [Parameter, EditorRequired] public CellLayoutProvider CellLayoutProvider { get; set; }

    private Sheet? _sheet;

    [Parameter, EditorRequired] public Sheet? Sheet { get; set; }

    [Parameter] public EventCallback<SelectionExpandedEventArgs> SelectionExpanded { get; set; }

    private bool _isDragging = false;
    private CellPosition? _dragStartPosition;

    private IRegion? _dragPreviewRegion;
    private double _dragStartX;
    private double _dragStartY;

    private string _bgStyle = "background:var(--selection-bg-color);";
    private IWindowEventService _windowEventService = null!;

    private IRegion? BoundedSelectingRegion => Sheet?.Selection.SelectingRegion?.GetIntersection(Sheet?.Region);

    private double GetLeft(IRegion region) => CellLayoutProvider.ComputeLeftPosition(region) + 1;
    private double GetTop(IRegion region) => CellLayoutProvider.ComputeTopPosition(region) + 1;
    private double GetWidth(IRegion region) => CellLayoutProvider.ComputeWidth(region) - 2;
    private double GetHeight(IRegion region) => CellLayoutProvider.ComputeHeight(region) - 2;

    protected override void OnParametersSet()
    {
        if (_sheet != Sheet)
        {
            if (_sheet != null)
            {
                _sheet.Selection.SelectionChanged -= OnSelectionChanged;
                _sheet.Selection.SelectingChanged -= OnSelectingChanged;
            }

            _sheet = Sheet;

            if (_sheet == null) return;

            _sheet.Selection.SelectionChanged += OnSelectionChanged;
            _sheet.Selection.SelectingChanged += OnSelectingChanged;
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _windowEventService = new WindowEventService(JS);
            await _windowEventService.Init();
            _windowEventService.OnMouseUp += WindowEventServiceOnOnMouseUp;
            _windowEventService.OnMouseMove += WindowEventServiceOnMouseMove;
        }
    }

    // Returns the region that the position covers (may be > 1 because of merged cells)
    private IRegion GetRegion(CellPosition position)
    {
        var merge = Sheet.Cells?.GetMerge(position.row, position.col);
        if (merge != null)
            return merge;
        else
            return new Region(position.row, position.col);
    }

    private string GetDraggerStyleString()
    {
        if (Sheet == null)
            return string.Empty;

        var region = Sheet.Selection.ActiveRegion ?? Sheet.Selection.SelectingRegion;
        if (region == null)
            return "display:none;";

        var x = CellLayoutProvider.ComputeLeftPosition(region.Right + 1);
        var y = CellLayoutProvider.ComputeTopPosition(region.Bottom + 1);
        var w = 6d;
        var h = 6d;
        var sb = new StringBuilder();
        sb.Append($"display:block;position:absolute;");
        sb.Append($"left:{x - w / 2}px;top:{y - w / 2}px;");
        sb.Append($"width:{w}px;height:{h}px;");
        sb.Append("background:var(--selection-border-color);");
        sb.Append("border:1px solid white;");
        return sb.ToString();
    }

    private void OnSelectionChanged(object? sender, IEnumerable<IRegion> regions)
    {
        StateHasChanged();
    }

    private void OnSelectingChanged(object? sender, IRegion? region)
    {
        StateHasChanged();
    }

    private async Task DraggerMouseDown(PointerEventArgs obj)
    {
        if (Sheet?.Selection.ActiveRegion == null)
            return;

        _isDragging = true;
        _dragStartPosition = Sheet.Selection.ActiveRegion.TopLeft;
        var position = await Sheet.InputService.GetInputPositionAsync();
        _dragStartX = position.X;
        _dragStartY = position.Y;
    }

    private async Task<bool> WindowEventServiceOnMouseMove(MouseEventArgs arg)
    {
        if (_isDragging)
        {
            var position = await _sheet!.InputService.GetInputPositionAsync();
            var x = position.X;
            var y = position.Y;
            var dx = Math.Abs(x - _dragStartX);
            var dy = Math.Abs(y - _dragStartY);

            var fillDirection = Direction.None;
            if (dx > dy && x >= _dragStartX)
                fillDirection = Direction.Right;
            else if (dx > dy && x < _dragStartX)
                fillDirection = Direction.Left;
            else if (dy > dx && y >= _dragStartY)
                fillDirection = Direction.Down;
            else if (dy > dx && y < _dragStartY)
                fillDirection = Direction.Up;

            var endPosition = GetCurrentDragEndPosition(fillDirection, x, y);
            _dragPreviewRegion = new Region(_dragStartPosition!.Value.row,
                endPosition.row,
                _dragStartPosition!.Value.col,
                endPosition.col);


            StateHasChanged();
        }

        return true;
    }

    private CellPosition GetCurrentDragEndPosition(Direction direction, double x, double y)
    {
        if (_sheet == null)
            return new CellPosition();

        var row = _sheet.Rows.GetRow(y);
        var col = _sheet.Columns.GetColumn(x);
        switch (direction)
        {
            case Direction.Right:
            case Direction.Left:
                return new(_sheet!.Selection!.ActiveRegion!.Bottom, col);
            case Direction.Up:
            case Direction.Down:
                return new(row, _sheet!.Selection!.ActiveRegion!.Right);
            default:
                return new(_dragStartPosition!.Value.row, _dragStartPosition!.Value.col);
        }
    }

    private async Task<bool> WindowEventServiceOnOnMouseUp(MouseEventArgs arg)
    {
        if (_isDragging && _dragPreviewRegion != null && _sheet?.Selection.ActiveRegion != null)
        {
            await SelectionExpanded.InvokeAsync(new SelectionExpandedEventArgs(_sheet.Selection.ActiveRegion.Clone(), _dragPreviewRegion.Clone()));
            _isDragging = false;
            _dragPreviewRegion = null;
            this.StateHasChanged();
        }

        return true;
    }

    public void Dispose()
    {
        _windowEventService?.Dispose();
    }

}